﻿using System;
using System.Collections.Generic;
using System.Text;

namespace RaisingStudio.Data
{
    /// <summary>
    /// Data item.
    /// </summary>
    [DefinitionName("DataItem")]
    [Serializable]
    public class DataItem
    {
        #region static member
        /// <summary>
        /// Gets DataItem Type.
        /// </summary>
        /// <param name="dataTable">Data item.</param>
        /// <param name="typeName">Type name.</param>
        /// <returns>DataItem Type.</returns>
        public static System.Type GetDataItemType(DataItem dataItem, System.Type type, string typeName)
        {
#if USE_TYPEMANAGER
            return RaisingStudio.Reflection.TypeManager.GetType(dataItem, typeName);
#else
            System.Type dataItemType;
            System.Reflection.Assembly assembly;
            if (typeName.IndexOf(",") >= 0)
            {
                string[] names = typeName.Split(',');
                assembly = System.Reflection.Assembly.LoadFrom(names[0]);
                typeName = names[1];
            }
            else
            {
                assembly = type.Assembly;
            }

            dataItemType = assembly.GetType(typeName);
            return dataItemType;
#endif
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="dataItem">data.</param>
        /// <param name="xmlElement">xml element.</param>
        public static void InitDataItem(DataItem dataItem, System.Type type, System.Xml.XmlElement xmlElement, bool keepValue)
        {
            #region Sets 项column 
            List<string> primaryKeyNameList = new List<string>(dataItem.PrimaryKeyNames);
            int index = dataItem.IndexDictionary.Count;
            foreach (System.Xml.XmlNode childXmlNode in xmlElement)
            {
                if (childXmlNode is System.Xml.XmlElement)
                {
                    System.Xml.XmlElement childXmlElement = childXmlNode as System.Xml.XmlElement;
                    if (childXmlElement.Name == "DataColumn")
                    {
                        // Column name.
                        string columnName = childXmlElement.Attributes["ColumnName"].Value.Trim();
                        dataItem.indexDictionary.Add(columnName, index);
                        #region 统计primary key 
                        if (childXmlElement.Attributes["PrimaryKey"] != null)
                        {
                            bool primaryKey = bool.Parse(childXmlElement.Attributes["PrimaryKey"].Value);
                            if (primaryKey)
                            {
                                primaryKeyNameList.Add(columnName);
                            }
                        }
                        #endregion
                        index++;
                    }
                }
            }
            #endregion

            #region Sets data 
            if (keepValue)
            {
                object[] originalValue = dataItem.ItemArray;
                object[] itemArray = new object[index];
                System.Array.Copy(originalValue, itemArray, originalValue.Length);
                dataItem.itemArray = itemArray;
            }
            else
            {
                dataItem.itemArray = new object[index];
            }
            #endregion

            #region Sets primary key 
            string[] primaryKeyNames = new string[primaryKeyNameList.Count];
            primaryKeyNameList.CopyTo(primaryKeyNames);
            dataItem.primaryKeyNames = primaryKeyNames;
            #endregion

            #region include Relation 
            if (xmlElement.Attributes["IncludeDefinition"] != null)
            {
                // Has include relation.
                string includeDefinitionName = xmlElement.Attributes["IncludeDefinition"].Value.Trim();

                if (xmlElement.Attributes["IncludeType"] != null)
                {
                    string includeTypeName = xmlElement.Attributes["IncludeType"].Value.Trim();
                    System.Type dataItemType = GetDataItemType(dataItem, type, includeTypeName);

                    InitDataItem(dataItem, dataItemType, includeDefinitionName);
                }
                else
                {
                    InitDataItem(dataItem, type, includeDefinitionName);
                }
            }
             #endregion
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="dataItem">data.</param>
        /// <param name="type">Type.</param>
        public static void InitDataItem(DataItem dataItem, System.Type type)
        {
            System.Xml.XmlElement xmlElement = DefinitionNameAttribute.LoadXmlDocument(type);
            InitDataItem(dataItem, type, xmlElement, false);
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="dataItem">data.</param>
        /// <param name="type">Type.</param>
        /// <param name="name">name.</param>
        public static void InitDataItem(DataItem dataItem, System.Type type, string name)
        {
            System.Xml.XmlElement xmlElement = DefinitionNameAttribute.LoadXmlDocument(type, name);
            InitDataItem(dataItem, type, xmlElement, false);
        }


        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="dataItem">data.</param>
        /// <param name="dataTable">table.</param>
        public static void InitDataItem(DataItem dataItem, System.Data.DataTable dataTable)
        {
            int index = 0;
            foreach (System.Data.DataColumn dataColumn in dataTable.Columns)
            {
                // Column name.
                string columnName = dataColumn.ColumnName;
                dataItem.indexDictionary.Add(columnName, index);
                index++;
            }
            dataItem.itemArray = new object[index];
            #region Sets primary key 
            string[] primaryKeyNames = new string[dataTable.PrimaryKey.Length];
            for (int i = 0; i < dataTable.PrimaryKey.Length; i++)
            {
                primaryKeyNames[i] = dataTable.PrimaryKey[i].ColumnName;
            }
            dataItem.primaryKeyNames = primaryKeyNames;
            #endregion
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        /// <param name="dataItem">data.</param>
        /// <param name="dataRow">row.</param>
        public static void InitDataItem(DataItem dataItem, System.Data.DataRow dataRow)
        {
            System.Data.DataTable dataTable = dataRow.Table;
            InitDataItem(dataItem, dataTable);
            dataItem.itemArray = dataRow.ItemArray;
        }
        

        /// <summary>
        /// Copy data into data object.
        /// </summary>
        /// <param name="dataItem">Data item.</param>
        /// <param name="dataObject">Data object.</param>
        /// <returns>Count of copyed.</returns>
        public static int CopyTo(DataItem dataItem, object dataObject)
        {
            System.Type dataObjectType = dataObject.GetType();
            int count = 0;
            foreach (string columnName in dataItem.ColumnNames)
            {
                System.Reflection.PropertyInfo propertyInfo = dataObjectType.GetProperty(columnName);
                if (propertyInfo != null)
                {
                    object value = dataItem[columnName];
                    if (value == System.DBNull.Value)
                    {
                        propertyInfo.SetValue(dataObject, null, null);
                    }
                    else
                    {
                        // TODO: convert.
                        propertyInfo.SetValue(dataObject, value, null);
                    }
                    count++;
                }
            }
            return count;
        }

        /// <summary>
        /// Copy from data object.
        /// </summary>
        /// <param name="dataItem">Data item.</param>
        /// <param name="dataObject">Data object.</param>
        /// <returns>Count of copyed.</returns>
        public static int CopyFrom(DataItem dataItem, object dataObject)
        {
            System.Type dataObjectType = dataObject.GetType();
            int count = 0;
            foreach (string columnName in dataItem.ColumnNames)
            {
                System.Reflection.PropertyInfo propertyInfo = dataObjectType.GetProperty(columnName);
                if (propertyInfo != null)
                {
                    dataItem[columnName] = propertyInfo.GetValue(dataObject, null);
                    count++;
                }
            }
            return count;
        }
        #endregion
 
        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        public DataItem()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        protected DataItem(bool clonal)
        {
            this.clonal = clonal;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Type.</param>
        public DataItem(System.Type type)
        {
            InitDataItem(this, type);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Type.</param>
        /// <param name="name">name.</param>
        public DataItem(System.Type type, string name)
        {
            InitDataItem(this, type, name);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="dataTable">table.</param>
        public DataItem(System.Data.DataTable dataTable)
        {
            InitDataItem(this, dataTable);
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="dataRow">row.</param>
        public DataItem(System.Data.DataRow dataRow)
        {
            InitDataItem(this, dataRow);
        }
       #endregion

        #region Common properties
        private bool clonal = false;
        /// <summary>
        /// Use the same structure of mode to create Data item.
        /// </summary>
        public bool Clonal
        {
            get
            {
                return this.clonal;
            }
        }

        private Dictionary<string, int> indexDictionary = new Dictionary<string,int>();
        /// <summary>
        /// Gets index dictionary.
        /// </summary>
        public Dictionary<string, int> IndexDictionary
        {
            get
            {
                return this.indexDictionary;
            }
        }

        /// <summary>
        /// Gets An array of Columns.
        /// </summary>
        public string[] ColumnNames
        {
            get
            {
                string[] columnNames = new string[this.indexDictionary.Keys.Count];
                this.indexDictionary.Keys.CopyTo(columnNames, 0);
                return columnNames;
            }
        }

        /// <summary>
        /// Gets value dictionary.
        /// </summary>
        public Dictionary<string, object> Values
        {
            get
            {
                Dictionary<string, object> values = new Dictionary<string, object>();
                foreach (string columnName in this.indexDictionary.Keys)
                {
                    values.Add(columnName, this.itemArray[this.indexDictionary[columnName]]);
                }
                return values;
            }
        }

        private object[] itemArray;
        /// <summary>
        /// Gets or sets all the values for this row through an array.
        /// </summary>
        public object[] ItemArray
        {
            get
            {
                return this.itemArray;
            }
            set
            {
                this.itemArray = value;
            }
        }

        /// <summary>
        /// Gets or sets the data stored in the column specified by index.
        /// </summary>
        /// <param name="index">index.</param>
        /// <returns>data.</returns>
        public object this[int index]
        {
            get
            {
                return this.itemArray[index];
            }
            set
            {
                this.itemArray[index] = value;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the specified column contains a null value.
        /// </summary>
        /// <param name="index">index.</param>
        /// <returns> true if the column contains a null value; otherwise, false.</returns>
        public bool IsNull(int index)
        {
            return (this[index] == System.Convert.DBNull);
        }

        /// <summary>
        /// Sets the value of the specified DataColumn to a null value.
        /// </summary>
        /// <param name="index">index.</param>
        public void SetNull(int index)
        {
            this[index] = System.Convert.DBNull;
        }
        
        /// <summary>
        /// Gets or sets the data stored in the column specified by name.
        /// </summary>
        /// <param name="key">name.</param>
        /// <returns>data.</returns>
        public object this[string columnName]
        {
            get
            {
                return this.itemArray[this.indexDictionary[columnName]];
            }
            set
            {
                this.itemArray[this.indexDictionary[columnName]] = value;
            }
        }
        
        /// <summary>
        /// Gets a value that indicates whether the named column contains a null value.
        /// </summary>
        /// <param name="key">name.</param>
        /// <returns> true if the column contains a null value; otherwise, false.</returns>
        public bool IsNull(string columnName)
        {
            return (this[columnName] == System.Convert.DBNull);
        }

        /// <summary>
        /// Sets the value of the specified named column to a null value.
        /// </summary>
        /// <param name="key">name.</param>
        public void SetNull(string columnName)
        {
            this[columnName] = System.Convert.DBNull;
        }

        private string[] primaryKeyNames;
        /// <summary>
        /// Gets or sets the names of primary keys.
        /// </summary>
        public string[] PrimaryKeyNames 
        {
            get
            {
                return this.primaryKeyNames;
            }
            set
            {
                this.primaryKeyNames = value;
            }
        }

        /// <summary>
        /// Gets or sets the values of primary keys.
        /// </summary>
        public object[] PrimaryKeys 
        {
            get
            {
                object[] primaryKeys = new object[this.primaryKeyNames.Length];
                for (int i = 0; i < primaryKeys.Length; i++)
                {
                    primaryKeys[i] = this[primaryKeyNames[i]];
                }
                return primaryKeys;
            }
            set
            {
                for (int i = 0; i < value.Length; i++)
                {
                    this[primaryKeyNames[i]] = value[i];
                }
            }
        }
        #endregion
        #region Common methods
        /// <summary>
        /// Create DataItem An instance.
        /// </summary>
        /// <returns>Data item.</returns>
        protected virtual DataItem CreateInstance()
        {            
            return System.Activator.CreateInstance(this.GetType()) as RaisingStudio.Data.DataItem;
        }

        /// <summary>
        /// Create same structure of Data item.
        /// </summary>
        /// <returns>Data item.</returns>
        public virtual DataItem Clone()
        {
            DataItem dataItem = CreateInstance();
            dataItem.indexDictionary = this.indexDictionary;
            dataItem.primaryKeyNames = this.primaryKeyNames;
            dataItem.itemArray = new object[dataItem.indexDictionary.Count];
            return dataItem;
        }

        /// <summary>
        /// Create same structure of Data item.
        /// </summary>
        /// <returns>Data item.</returns>
        public virtual DataItem DeepClone()
        {
            DataItem dataItem = CreateInstance();
            foreach (string columnName in this.indexDictionary.Keys)
            {
                dataItem.indexDictionary.Add(columnName, this.indexDictionary[columnName]);
            }
            dataItem.primaryKeyNames = new string[this.primaryKeyNames.Length];
            for (int i = 0; i < this.primaryKeyNames.Length; i++)
            {
                dataItem.primaryKeyNames[i] = this.primaryKeyNames[i];
            }
            dataItem.itemArray = new object[dataItem.indexDictionary.Count];
            return dataItem;
        }

        /// <summary>
        /// Create same structure and content of Data item.
        /// </summary>
        /// <returns>Data item.</returns>
        public virtual DataItem Copy()
        {
            DataItem dataItem = CreateInstance();
            dataItem.indexDictionary = this.indexDictionary;
            dataItem.primaryKeyNames = this.primaryKeyNames;
            dataItem.itemArray = new object[this.indexDictionary.Count];
            for (int i = 0; i < this.indexDictionary.Count; i++)
            {
                dataItem.itemArray[i] = this.itemArray[i];
            }
            return dataItem;
        }

        /// <summary>
        /// Create same structure and content of Data item.
        /// </summary>
        /// <returns>Data item.</returns>
        public virtual DataItem DeepCopy()
        {
            DataItem dataItem = CreateInstance();
            dataItem.itemArray = new object[this.indexDictionary.Count];
            dataItem.primaryKeyNames = new string[this.primaryKeyNames.Length];
            for (int i = 0; i < this.primaryKeyNames.Length; i++)
            {
                dataItem.primaryKeyNames[i] = this.primaryKeyNames[i];
            }
            int index = 0;
            foreach (string columnName in this.indexDictionary.Keys)
            {
                dataItem.indexDictionary.Add(columnName, this.indexDictionary[columnName]);
                dataItem.itemArray[index] = this.itemArray[index];
                index++;
            }
            return dataItem;
        }
        #endregion
    }
}
